#include <linux/module.h>
#include <linux/kernel.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_driver.h>
#include "common.h"
#include "HBA_driver.h"

MODULE_AUTHOR("Arkins");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Native SCSI Disk Driver Adapter");

#define NDA_DRIVER_NAME "nda"
#define NDA_TARGET_DRV_NAME "sd"
#define TP_MAJOR 110
static int NDA_CustomProbe(struct device *dev);
static int NDA_CustomRemove(struct device *dev);

// 系统自带驱动的函数指针
static int (*NDA_NativeProbe)(struct device *dev) = NULL;
static int (*NDA_NativeRemove)(struct device *dev) = NULL;
// static blk_status_t (*NDA_NativeInitCommand)(struct scsi_cmnd *cmnd) = NULL;

struct scsi_device *sdp = NULL;
struct gendisk *gd      = NULL;

static struct lock_class_key tp_bio_compl_lkclass;
#define TP_MAX_RETRIES		5
#define TP_MAX_MEDIUM_TIMEOUTS	2
#define TP_TIMEOUT		(30 * HZ)
#define TP_MOD_TIMEOUT		(75 * HZ)


// 系统自带的SD驱动适配器
static struct scsi_driver g_ndaDriver = {
	.gendrv = {
		.name		= NDA_DRIVER_NAME,
		.owner		= THIS_MODULE,
		.probe		= NDA_CustomProbe,
		.remove		= NDA_CustomRemove,
		.probe_type	= PROBE_PREFER_ASYNCHRONOUS
	},
	.rescan			= NULL,
	.init_command	= NULL,
	.uninit_command	= NULL,
	.done			= NULL,
	.eh_action		= NULL,
	.eh_reset		= NULL,
};

static int tp_probe(struct device *dev)
{
	int index;
	int error;

	sdp = to_scsi_device(dev);
	error = -ENODEV;
	if (sdp->type != TYPE_DISK &&
	    sdp->type != TYPE_ZBC &&
	    sdp->type != TYPE_MOD &&
	    sdp->type != TYPE_RBC)
		goto out;

	if (!IS_ENABLED(CONFIG_BLK_DEV_ZONED) && sdp->type == TYPE_ZBC) {
		sdev_printk(KERN_WARNING, sdp,
			    "Unsupported ZBC host-managed device.\n");
		goto out;
	}
	error = -ENOMEM;
	gd = blk_mq_alloc_disk_for_queue(sdp->request_queue,
					 &tp_bio_compl_lkclass);
	if (!gd)
		goto out;

	gd->disk_name[0] = 'T';
	gd->disk_name[1] = 'P';
	gd->disk_name[2] = '\0';
	TP_LOG_INFO("gd->disk_name=%s", gd->disk_name);
	if (!sdp->request_queue->rq_timeout) {
		if (sdp->type != TYPE_MOD)
			blk_queue_rq_timeout(sdp->request_queue, TP_TIMEOUT);
		else
			blk_queue_rq_timeout(sdp->request_queue,
					     TP_MOD_TIMEOUT);
	}
	gd->major = TP_MAJOR;
	gd->first_minor = 0;
	gd->minors = 1;

	gd->fops = NULL;
	gd->private_data = NULL;

	/* defaults, until the device tells us otherwise */
	sdp->sector_size = 512;

	if (sdp->removable) {
		gd->flags |= GENHD_FL_REMOVABLE;
		gd->events |= DISK_EVENT_MEDIA_CHANGE;
		gd->event_flags = DISK_EVENT_FLAG_POLL | DISK_EVENT_FLAG_UEVENT;
	}
	error = device_add_disk(dev, gd, NULL);
	TP_LOG_INFO("TP_PROBE - error %d", error);
	if (error) {
		put_disk(gd);
		goto out;
	}
	TP_LOG_INFO("TP_PROBE");
	return 0;

 out_put:
	put_disk(gd);
 out:
	return error;
}

static int NDA_PrintDevice(struct device *dev)
{
	struct scsi_device *sdp;
	const char *dst = "VMware";
	const char *HBA_SCSI_MODEL = "TP";

	sdp = to_scsi_device(dev);
	TP_CHECK_EXPRESSION(sdp == NULL, return -1);
	TP_CHECK_EXPRESSION(sdp->vendor == NULL, return -1);
	TP_LOG_INFO("vendor=%s", sdp->vendor);
	TP_LOG_INFO("model=%s", sdp->model);
	TP_LOG_INFO("H:C:T:L=%u:%u:%u:%llu", sdp->host->host_no, sdp->channel, sdp->id, sdp->lun);

	if (strncmp(sdp->vendor, HBA_SCSI_MODEL, strlen(HBA_SCSI_MODEL)) == 0) {
		TP_LOG_INFO("vendor=%s", sdp->vendor);
		TP_LOG_INFO("model=%s", sdp->model);
		TP_LOG_INFO("H:C:T:L=%u:%u:%u:%llu", sdp->host->host_no, sdp->channel, sdp->id, sdp->lun);
		return 0;
	}
	if (strncmp(sdp->vendor, dst, strlen(dst)) == 0) {
		TP_LOG_INFO("VMWare");
		return 1;
	}
	return 0;
}

static int NDA_CustomProbe(struct device *dev)
{
	int ret = 0;
	TP_CHECK_EXPRESSION(dev == NULL, return 0)
	if (NDA_NativeProbe == NULL) {
		return TP_OK;
	}
	ret = NDA_PrintDevice(dev);
	if (ret == 1) {
		// tp_probe(dev);
		HBA_CreateVlun(sdp, 1, 1, 1);
		ret = 0;
	} else {
		ret = NDA_NativeProbe(dev);
	}
	printk("%d\n", ret);
	return ret;
}

static int NDA_CustomRemove(struct device *dev)
{
	if (NDA_NativeRemove == NULL) {
		return TP_OK;
	}
	return NDA_NativeRemove(dev);
}

static int NDA_ReplaceDriverFunc(struct device_driver *drv, void *data)
{
	struct scsi_driver *sdrv;
	TP_CHECK_EXPRESSION(drv->name == NULL, return 0);

	if (strcmp(drv->name, NDA_TARGET_DRV_NAME) != 0) {
		return TP_OK;
	}
	NDA_NativeProbe = drv->probe;
	NDA_NativeRemove = drv->remove;
	drv->probe = NDA_CustomProbe;
	drv->remove = NDA_CustomRemove;

	sdrv = to_scsi_driver(drv);
	TP_CHECK_EXPRESSION(sdrv == NULL, return TP_ERROR);
	// NDA_NativeInitCommand = sdrv->init_command;

	g_ndaDriver.init_command = sdrv->init_command;
	g_ndaDriver.uninit_command = sdrv->uninit_command;
	g_ndaDriver.eh_action = sdrv->eh_action;
	g_ndaDriver.eh_reset = sdrv->eh_reset;
	TP_LOG_INFO("Driver native function successfully replaced.");
	return TP_OK;
}

static int NDA_RestoreDriverFunc(struct device_driver *drv, void *data)
{
	TP_CHECK_EXPRESSION(drv->name == NULL, return 0);

	if (strcmp(drv->name, NDA_TARGET_DRV_NAME) != 0) {
		return TP_OK;
	}
	if (NDA_NativeProbe != NULL) {
		drv->probe = NDA_NativeProbe;
		NDA_NativeProbe = NULL;
	}
	if (NDA_NativeRemove != NULL) {
		drv->remove = NDA_NativeRemove;
		NDA_NativeRemove = NULL;
	}
	TP_LOG_INFO("Driver native function successfully restored.");
	return TP_OK;
}

static int NDA_RegisterDriver(void)
{
	int ret;
    ret = scsi_register_driver(&g_ndaDriver.gendrv);
    if (ret || g_ndaDriver.gendrv.bus == NULL) {
        TP_LOG_INFO("scsi_register_driver error\n");
		return TP_ERROR;
    }
	ret = bus_for_each_drv(g_ndaDriver.gendrv.bus, NULL, NULL, NDA_ReplaceDriverFunc);
	if (ret != TP_OK)
		return TP_ERROR;
	return TP_OK;
}

static void NDA_UnRegisterDriver(void)
{
	bus_for_each_drv(g_ndaDriver.gendrv.bus, NULL, NULL, NDA_RestoreDriverFunc);
    scsi_unregister_driver(&g_ndaDriver.gendrv);
}

static void tp_default_probe(dev_t devt)
{
}

int NDA_DriverInit(void)
{
	int ret;
	ret = __register_blkdev(TP_MAJOR, "tp", tp_default_probe);
	printk("__register_blkdev=%d\n", ret);
	ret = NDA_RegisterDriver();
	if (ret != TP_OK) {
		NDA_UnRegisterDriver();
		return TP_ERROR;
	}
	return TP_OK;
}

void NDA_DriverExit(void)
{
	unregister_blkdev(TP_MAJOR, "tp");
	NDA_UnRegisterDriver();
}
